// CLIENT TCP PROGRAM
// Revised and tidied up by
// J.W. Atwood
// 1999 June 30

/*
 * Modified for COMP 445 A1
 * Student: Jacqueline Fu and Kevin Labrie
 * Last modify date: Sep. 22th 2012 at 5:19pm
 * Professors: Dr. Ebrahim Soujeri & Dr. Aiman Hanna
 */

/* send and receive codes between client and server */
/* This is your basic WINSOCK shell */
#pragma comment( linker, "/defaultlib:ws2_32.lib" )
#include <winsock2.h>
#include <ws2tcpip.h>
#include <winsock.h>
#include <stdio.h>
#include <iostream>
#include <string.h>
#include <windows.h>
//==================
#include <fstream>
#include <string>
#include <ctime>
#include "..\router\Router.h"
//==================

using namespace std;

//user defined port number
#define REQUEST_PORT 5000;

int port=REQUEST_PORT;

#define TRACE 1

//socket data types
SOCKET s;
SOCKADDR_IN sa;         // client info, IP, port 5000	// filled by bind
SOCKADDR_IN sa_in;      // router info, IP, port 7000	// fill with server info, IP, port

int const MAX_SIZE = 128;
int const MAX = 260;
//buffer data types
char szbuffer[128];
char *buffer;
int ibufferlen=0;
int ibytessent;
//int msgReceived=0;
int ibytesrecv;

//host data types
HOSTENT *hp;
HOSTENT *rp;

char localhost[11],
     remotehost[11];

//other
HANDLE test;
DWORD dwtest;

//wait variables -> ADDED ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
int nsa1;
int r,infds=1, outfds=0;
struct timeval timeout;
const struct timeval *tp=&timeout;
	fd_set readfds;
//variables for the timer
#define UTIMER 300000
#define STIMER 0 
struct timeval timeouts;

//more variables
string quit;
char direction[3]; //variable to hold direction of transfer i.e. get, put, list or delete
int bitsread;
int bitsleft;
int servernum;
int clientnum;
int clientseqnum;
int serverseqnum;

//reference for used structures

/*  * Host structure

    struct  hostent {
    char    FAR * h_name;             official name of host *
    char    FAR * FAR * h_aliases;    alias list *
    short   h_addrtype;               host address type *
    short   h_length;                 length of address *
    char    FAR * FAR * h_addr_list;  list of addresses *
#define h_addr  h_addr_list[0]            address, for backward compat *
};

 * Socket address structure

 struct sockaddr_in {
 short   sin_family;
 u_short sin_port;
 struct  in_addr sin_addr;
 char    sin_zero[8];
 }; sa_in*/

// ADDED FOR 3-WAY-HANDSHAKE
//#define STOPNWAIT 1 //Bits width For stop and wait
struct MESSAGE_FRAME 
{
	unsigned char header;		//ERROR,DATA,LASTPACKET, etc.
	unsigned int snwseq;//:STOPNWAIT;
	char data[MAX_SIZE]; 		//data or error message
} message_frame;

struct THREE_WAY_HS
{
	int client_number;
	int server_number;
	char direction;
	char file_name[MAX];
} three_way_hs;

struct DATA_FRAME 
{
	unsigned char header;		//ERROR,DATA,LASTPACKET, etc.
	char data[260]; 		//data or error message
} data_frame;


int main(void)
{
	// Handshake
	srand((unsigned)time(NULL));

	timeouts.tv_sec=STIMER;
	timeouts.tv_usec=UTIMER;
	
	//create log file and open for writting
	FILE* filelog;
	filelog = fopen("clientlog.txt", "w");
	
	WSADATA wsadata;

	try 
	{
		if (WSAStartup(0x0202,&wsadata)!=0)
		{  
			cout<<"Error in starting WSAStartup()" << endl;
		} 
		else 
		{
			buffer="WSAStartup was successful\n";   
			WriteFile(test,buffer,sizeof(buffer),&dwtest,NULL); 

			/* Display the wsadata structure */
			cout<< endl
				<< "wsadata.wVersion "       << wsadata.wVersion       << endl
				<< "wsadata.wHighVersion "   << wsadata.wHighVersion   << endl
				<< "wsadata.szDescription "  << wsadata.szDescription  << endl
				<< "wsadata.szSystemStatus " << wsadata.szSystemStatus << endl
				<< "wsadata.iMaxSockets "    << wsadata.iMaxSockets    << endl
				<< "wsadata.iMaxUdpDg "      << wsadata.iMaxUdpDg      << endl;
		}
		
		//pa1 starts
		//Display name of local host.
		gethostname(localhost,10);
		cout << "ftp_tcp starting at host: [" << localhost << "]" << endl;

		if((hp=gethostbyname(localhost)) == NULL) 
			throw "gethostbyname failed\n";

		//Ask for name of remote server
		cout << "please enter your remote server name: " << flush ;   
		cin >> remotehost ;
		
		//type q to exit program
		if(strcmp(remotehost, "q") == 0)
			exit(0);

		cout << "Remote host name is: \"" << remotehost << "\"" << endl;

		if((rp=gethostbyname(remotehost)) == NULL)
			throw "remote gethostbyname failed\n";

		//Create the socket
		if((s = socket(AF_INET,SOCK_DGRAM,0))==INVALID_SOCKET) 
			throw "Socket failed\n";
		/* For UDP protocol replace SOCK_STREAM with SOCK_DGRAM */

		//Fill-in Client Port and Address info.
		memset(&sa,0,sizeof(sa));
		sa.sin_family = AF_INET;
		sa.sin_port = htons(PEER_PORT1);
		sa.sin_addr.s_addr = htonl(INADDR_ANY); //host to network
		//bind the port to the socket
		if (bind(s,(LPSOCKADDR)&sa,sizeof(sa)) == SOCKET_ERROR)
			throw "can't bind the socket";

		//Specify server address for client to connect to server.
		memset(&sa_in,0,sizeof(sa_in));
		memcpy(&sa_in.sin_addr,rp->h_addr,rp->h_length);
		sa_in.sin_family = rp->h_addrtype;   
		sa_in.sin_port = htons(ROUTER_PORT1);//7000);//port);

		//Display the host machine internet address
		cout << "Connecting to remote host: ";
		cout << inet_ntoa(sa_in.sin_addr) << endl;

		//Connect Client to the server
		if (connect(s,(LPSOCKADDR)&sa_in,sizeof(sa_in)) == SOCKET_ERROR)
			throw "connect failed\n";

		FD_ZERO(&readfds);

		FD_SET(s,&readfds);

		//length of sockaddr struct
		int len = sizeof(struct sockaddr);

		while(true)
		{
			//create random client number for three way handsahke
			clientnum = rand() % 256;
			three_way_hs.client_number = clientnum;
			
			//clear direction variable
			memset(direction, 0, sizeof(direction));

			//make sure input from user is get, put, lst or del or else keep asking
			while(!strcmp(direction, "get") == 0 && !strcmp(direction, "put") == 0 && !strcmp(direction, "del") == 0
				&& !strcmp(direction, "lst") == 0)
			{
				cout << "What do you want to do? (get/put/del/lst): ";
				//get direction of transfer from user
				cin >> direction;

				//type q to exit program
				if(strcmp(direction, "q") == 0)
					exit(0);
			}

			//in three_way_hs list is l, delete is char d, get is char g and put is char p
			if(strcmp(direction, "lst") == 0)
				three_way_hs.direction = 'l';
			else if(strcmp(direction, "del") == 0)
				three_way_hs.direction = 'd';
			else if(strcmp(direction, "get") == 0)
					three_way_hs.direction = 'g';
			else if(strcmp(direction, "put") == 0)
				three_way_hs.direction = 'p';

			if(three_way_hs.direction != 'l') //dont ask for file name when getting directory listing
			{
				//Ask for name of file
				cout << "Type name of file: ";
				cin >> three_way_hs.file_name;
			}

			//type q to exit program
			if(strcmp(three_way_hs.file_name, "q") == 0)
				exit(0);

			//if direction is put check the file exists on the clients computer
			if(three_way_hs.direction == 'p')
			{
				//try to open requested file
				ifstream checkfile;
				checkfile.open(three_way_hs.file_name, ios_base::in | ios_base::binary );

				//keep asking for filename until it exists
				while(!checkfile.is_open()) //checkfile doesn't exist
				{
					//close file
					checkfile.close();

					//clear three_way_hs.file_name
					memset(three_way_hs.file_name, 0, sizeof(three_way_hs.file_name));

					//Ask for name of file
					cout << "Type name of file to be transferred: ";
					cin >> three_way_hs.file_name;

					//type q to exit program
					if(strcmp(three_way_hs.file_name, "q") == 0)
						exit(0);

					//try to open the file
					checkfile.open(three_way_hs.file_name, ios_base::in | ios_base::binary );
				}

				//close file
				checkfile.close();
			}
		
			//start of three way handshake

			//timeout loop to recieve server number in three way handshake
			while(true)
			{
				FD_ZERO(&readfds);

				FD_SET(s,&readfds);

				//send three_way_hs with client number to server
				//form of sendto(socket to send result, the datagram result to send, the datagram length, flags: no options, addr, addr length);
				sendto(s, (char*)&three_way_hs, sizeof(three_way_hs), 0, (struct sockaddr*)&sa_in, sizeof(sa_in));

				//timeout is 300ms
				outfds = select (1 , &readfds, NULL, NULL,&timeouts);

				if (outfds) //if outfds is 1, data has been recieved
					break;

				//if outfds is 0, no data received yet so does nothing and keeps waiting
			}

			//recieve server number in three way handshake
			//form of recvfrom(socket, buffer, length of buffer, flags, socket address, length of socket address);
			ibytesrecv = recvfrom(s, (char*)&three_way_hs, sizeof(three_way_hs), 0, (struct sockaddr*)&sa_in, &len);

			//check for error
			if(ibytesrecv == SOCKET_ERROR)
				throw "Receive error in server program\n";
			if(clientnum != three_way_hs.client_number) //check if recieved client number is the same as first created
				throw "Receive error in server program\n";

			//send ack to finish three way handshake
			sendto(s, (char*)&three_way_hs, sizeof(three_way_hs), 0, (struct sockaddr*)&sa_in, sizeof(sa_in));

			//threee way handshake complete!

			//determine initial sequence numbers (0 if number is even, 1 if it is odd)
			clientseqnum = three_way_hs.server_number % 2;
			serverseqnum = three_way_hs.client_number % 2;
			
			if(three_way_hs.direction == 'l')
			{
				do
				{
					//clear message_frame.data
					memset(message_frame.data, 0, sizeof(message_frame.data));

					//timeout loop to recieve directory file name
					while(true)
					{
						FD_ZERO(&readfds);

						FD_SET(s,&readfds);
						//timeout is 0 therefore waits forever until it recieves something
						outfds = select (1 , &readfds, NULL, NULL, &timeouts);

						if (outfds) //if outfds is 1, data has been recieved
							break;

						//if outfds is 0, no data received yet so does nothing and keeps waiting
					}

					//recieve directory file name
					ibytesrecv = recvfrom(s, (char*)&message_frame, sizeof(message_frame), 0, (struct sockaddr*)&sa_in, &len);

					//print info to log
					if(TRACE)
					{
						fprintf(filelog, "Receiver: receive packet %d\n", message_frame.snwseq);
					}

					//check for error else recieve packets
					if(ibytesrecv == SOCKET_ERROR)
						throw "Receive error in server program\n";

					//send ack to server
					sendto(s, (char*)&message_frame, sizeof(message_frame), 0, (struct sockaddr*)&sa_in, sizeof(sa_in));

					//print info to log
					if(TRACE)
					{
						fprintf(filelog, "Receiver: sent an ACK for packet %d\n", message_frame.snwseq);
					}

					//if message is in seq use else don't use it and wait for next packet
					if(message_frame.snwseq == serverseqnum)
					{
						switch (message_frame.header)
						{
							case 'e':
								//display message that no files on the server
								cout <<"No Files On The Server!" << endl;
								break;
							case 'f':
								//display file name from server
								cout << message_frame.data << endl;
								break;
							case 'l':
								//print info to log
								if(TRACE)
								{
									fprintf(filelog, "Receiver: transfer completed");
								}
								//no more files to display
								break;
						}

						//add 1 to seqnum
						serverseqnum = serverseqnum + 1;
					}
				}
				while(message_frame.header != 'e' && message_frame.header != 'l');
			}
			else if(three_way_hs.direction == 'd') //if direction is delete i.e. char d, delete file from the server
			{
				//timeout loop to recieve delete success/fail
				while(true)
				{
					FD_ZERO(&readfds);

					FD_SET(s,&readfds);
					//timeout is 300ms
					outfds = select (1 , &readfds, NULL, NULL, &timeouts);

					if (outfds) //if outfds is 1, data has been recieved
						break;

					//if outfds is 0, no data received yet so does nothing and keeps waiting
				}

				//recieve directory file name
				ibytesrecv = recvfrom(s, (char*)&message_frame, sizeof(message_frame), 0, (struct sockaddr*)&sa_in, &len);

				if(TRACE)
				{
					fprintf(filelog, "Receiver: receive packet %d\n", message_frame.snwseq);
				}

				//check for error else recieve packets
				if(ibytesrecv == SOCKET_ERROR)
					throw "Receive error in server program\n";

				//send ack to server
				sendto(s, (char*)&message_frame, sizeof(message_frame), 0, (struct sockaddr*)&sa_in, sizeof(sa_in));

				//print info to log
				if(TRACE)
				{
					fprintf(filelog, "Receiver: sent an ACK for packet %d\n", message_frame.snwseq);
				}

				if(message_frame.snwseq == serverseqnum)
				{
					switch (message_frame.header)
					{
						case 'e':
							//display message that file doesn't exist on the server
							cout <<"ERROR: File Does Not Exist!" << endl;
							cout << "File " << three_way_hs.file_name << " cannot be deleted from the server ";

							//print info to log
							if(TRACE)
							{
								fprintf(filelog, "Receiver: transfer completed");
							}

							cout << remotehost << "!" << endl;
							break;
						case 's':
							//print info to log
							if(TRACE)
							{
								fprintf(filelog, "Receiver: transfer completed");
							}

							//display message that file was deleted from the server
							cout << "SUCCESS: File Deleted From Server!" << endl;
							break;
					}

					//add 1 to seqnum
					serverseqnum = serverseqnum + 1;
				}
			}
			else if(three_way_hs.direction == 'g') //if direction is get i.e. char g, retrieve file to server
			{
				//message saying file is being received
				cout << "Receiving file from " << remotehost << ", waiting..." << endl;

				//create file to write to as filein
				ofstream filein;

				//open for output in binary mode (overwrite file if it already exists)
				filein.open(three_way_hs.file_name, ios::out | ios::binary );

				//bool to flag when the file as been completely received
				bool lastpacket = false;

				//continue recieving packets of file until none are left i.e. receive packet indicating end of file
				while(lastpacket == false)
				{
					//timeout loop to recieve file packet
					while(true)
					{
						FD_ZERO(&readfds);

						FD_SET(s,&readfds);

						//timeout is 300ms
						outfds = select (1 , &readfds, NULL, NULL, &timeouts);

						if (outfds) //if outfds is 1, data has been recieved
							break;

						//if outfds is 0, no data received yet so does nothing and keeps waiting
					}

					//recieve file packet
					ibytesrecv = recvfrom(s, (char*)&message_frame, sizeof(message_frame), 0, (struct sockaddr*)&sa_in, &len);

					//print info to log
					if(TRACE)
					{
						fprintf(filelog, "Receiver: receive packet %d\n", message_frame.snwseq);
					}

					//check for error else recieve packets
					if(ibytesrecv == SOCKET_ERROR)
						throw "Receive error in server program\n";

					//send ack to server
					sendto(s, (char*)&message_frame, sizeof(message_frame), 0, (struct sockaddr*)&sa_in, sizeof(sa_in));

					//print info to log
					if(TRACE)
					{
						fprintf(filelog, "Receiver: sent an ACK for packet %d\n", message_frame.snwseq);
					}

					if(message_frame.snwseq == serverseqnum)
					{
						switch (message_frame.header)
						{
							case '9':
								//close file
								filein.close();
							
								//delete file since it doesn't exist on the server
								remove(three_way_hs.file_name);

								//display message that file doesn't exist on the server
								cout <<"ERROR: File Does Not Exist!" << endl;
								cout << "File " << three_way_hs.file_name << " cannot be retrieved from the server ";
								cout << remotehost << "!" << endl;

								//flag that this is the last packet
								lastpacket = true;
								break;
							case 'b': //beginning or middle of file
								//write packet to file
								filein.write (message_frame.data, sizeof(message_frame.data));
								
								//flag that this is not the last packet
								lastpacket = false;
								break;
							default: //end of file
								//write packet to file
								filein.write (message_frame.data, (int)message_frame.header);

								//flag that this is the last packet
								lastpacket = true;
								break;
						}

						//add 1 to seqnum
						serverseqnum = serverseqnum + 1;
					}
				}

				//success unless recieved error
				if(message_frame.header != '9')
				{
					//get file size
					filein.seekp(0, ios::end);
					int filesizein = filein.tellp();

					//close the file
					filein.close();

					//print info to log
					if(TRACE)
					{
						fprintf(filelog, "Receiver: transfer completed");
					}

					//print info to log
					if(TRACE)
					{
						fprintf(filelog, "Reciever: number of bytes received: %d\n", filesizein);
					}

					//message saying file has been received
					cout << "File " << three_way_hs.file_name << " received from host " << remotehost << "!" << endl;
				}
			}
			else if(three_way_hs.direction == 'p') //direction is put i.e. char p, send file to server to be stored
			{
				//message saying file is being sent
				cout << "Sending file to " << remotehost << ", waiting..." << endl;

				//open file to be sent as fileout 
				ifstream fileout;

				//open for input in binary mode
				fileout.open(three_way_hs.file_name, ios_base::in | ios_base::binary );

				//check if the requested file exists (this should never happen, just a double check)
				if (!fileout.is_open()) //file does not exit
				{                 
					//if file does not exist: error message
					cout <<"ERROR: File Does Not Exist!" << endl;
				}
				else //file exists
				{
					//get the file size
					fileout.seekg (0, ios::end);
					int filesize = fileout.tellg();
					fileout.seekg (0, ios::beg);

					//keep track of the bits already read
					bitsread = 0;

					//keep track of the bits left to read
					bitsleft = filesize;

					//bool to flag when the file as been completely received
					bool lastpacket = false;

					//keep sending packets containing the file data until there are no more bits to read
					while(lastpacket == false)
					{
						//if data is at the begining or middle of the file
						if(bitsleft > MAX_SIZE)
						{
							//read data into the packet until the alloted size is reached
							fileout.read(message_frame.data, MAX_SIZE);

							while(true)
							{
								//char 1 means this packet is in the middle
								message_frame.header = 'b';

								message_frame.snwseq = clientseqnum; //send seq num

								//send packet to server
								//send(s, (char *)&message_frame, sizeof(message_frame), 0);
								sendto(s, (char*)&message_frame, sizeof(message_frame), 0, (struct sockaddr*)&sa_in, sizeof(sa_in));

								//print info to log
								if(TRACE)
								{
									fprintf(filelog, "Sender: sent packet %d\n", message_frame.snwseq);
								}

								//timeout is 300ms
								outfds = select (1 , &readfds, NULL, NULL, &timeouts);

								if (outfds) //if outfds is 1, data has been recieved
								{
									//recieve client ack to finish three way handshake
									ibytesrecv = recvfrom(s, (char*)&message_frame, sizeof(message_frame), 0, (struct sockaddr*)&sa_in, &len);

									//print info to log
									if(TRACE)
									{
										fprintf(filelog, "Sender: received ACK for packet %d\n", message_frame.snwseq);
									}

									//check for error
									if(ibytesrecv == SOCKET_ERROR)
										throw "Receive error in server program\n";

									//check if seqnum are the same
									if(clientseqnum == message_frame.snwseq) //if yes packet was received by client
									{
										//add 1 to seqnum
										clientseqnum = clientseqnum + 1;
										break;
									}

									//else resend packet
								}
							}

							//change bits read and bits left accordingly
							bitsleft = bitsleft - MAX_SIZE;
							bitsread = bitsread + MAX_SIZE;

							cout << "data: " << message_frame.data << endl;

							//flag that this is not the last packet
							lastpacket = false;
						}
						else //if data is at the end of the file
							if(bitsleft <= MAX_SIZE)
							{
								//since we will not fill the entire message_frame.data, clear it
								memset(message_frame.data, 0, sizeof(message_frame.data));

								//read data into the packet until the alloted size is reached
								fileout.read(message_frame.data, bitsleft);

								while(true)
								{
									//char 2 means this packet is the last
									message_frame.header = (unsigned char)bitsleft;

									cout << "bitsleft: " << bitsleft <<" and "<< (unsigned char)bitsleft << endl;
									cout << "header: " << message_frame.header << endl;

									message_frame.snwseq = clientseqnum; //send seq num

									//send packet to server
									//send(s, (char *)&message_frame, sizeof(message_frame), 0);
									sendto(s, (char*)&message_frame, sizeof(message_frame), 0, (struct sockaddr*)&sa_in, sizeof(sa_in));

									//print info to log
									if(TRACE)
									{
										fprintf(filelog, "Sender: sent packet %d\n", message_frame.snwseq);
									}

									//timeout is 300ms
									outfds = select (1 , &readfds, NULL, NULL, &timeouts);

									if (outfds) //if outfds is 1, data has been recieved
									{
										//recieve client ack to finish three way handshake
										ibytesrecv = recvfrom(s, (char*)&message_frame, sizeof(message_frame), 0, (struct sockaddr*)&sa_in, &len);

										//print info to log
										if(TRACE)
										{
											fprintf(filelog, "Sender: received ACK for packet %d\n", message_frame.snwseq);
										}

										//check for error
										if(ibytesrecv == SOCKET_ERROR)
											throw "Receive error in server program\n";

										//check if seqnum are the same
										if(clientseqnum == message_frame.snwseq) //if yes packet was received by client
										{
											//add 1 to seqnum
											clientseqnum = clientseqnum + 1;
											break;
										}

										//else resend packet
									}
								}

								//change bits read and bits left accordingly
								bitsleft = bitsleft - bitsleft;
								bitsread = bitsread + bitsleft;

								cout << "data: " << message_frame.data << endl;
								cout << "header: " << message_frame.header << endl;

								//flag that this is the last packet
								lastpacket = true;
							}
					}
				}

				//get file size
				fileout.seekg(0, ios::end);
				int filesizeout = fileout.tellg();

				//close the file
				fileout.close();

				//print info to log
				if(TRACE)
				{
					fprintf(filelog, "Sender: transfer completed");
				}

				//print info to log
				if(TRACE)
				{
					fprintf(filelog, "Sender: number of bytes received: %d\n", filesizeout);
				}

				//message saying file has been sent
				cout << "File " << three_way_hs.file_name << " send to host " << remotehost << "!" << endl;
			}


			//quit or contiune
			//keep asking until y or n
			while(quit != "n" && quit != "y")
			{
				cout << "Transfer another file? (y/n): ";
				cin >> quit;
			}

			//if n quit
			if(quit == "n")
			{	
				cout << "System is quitting..." << endl;

				//send three_way_hs to server indicating quit
				three_way_hs.direction = 'q';
				//send(s, (char *)&three_way_hs, sizeof(three_way_hs), 0);
				sendto(s, (const char*)&three_way_hs, sizeof(three_way_hs), 0, (struct sockaddr*)&sa_in, sizeof(sa_in));

				//system("pause");
				break;
			}

			quit = "";

		}//while loop

		//system("pause");

	} // try loop

	//Display any needed error response.

	catch (char *str) { cerr<<str<<":"<<dec<<WSAGetLastError()<<endl;}

	//close the client socket
	closesocket(s);

	/* When done uninstall winsock.dll (WSACleanup()) and exit */ 
	WSACleanup();

	//close log file
	fclose (filelog);

	return 0;
}